#include "userprog/syscall.h"
#include "userprog/pagedir.h"
#include "userprog/process.h"
#include <stdio.h>
#include <syscall-nr.h>
#include "threads/interrupt.h"
#include "threads/thread.h"
#include "devices/shutdown.h"
#include "threads/vaddr.h"
#include "threads/synch.h"
#include "userprog/exception.h"
#include "filesys/file.h"
#include "filesys/filesys.h"
#include "devices/input.h"


#define ARG1  (f->esp+4)
#define ARG2  (f->esp+8)
#define ARG3  (f->esp+12)



static void      syscall_handler (struct intr_frame *);
static bool      check_pointer(void* point);
static void      run_SYS_EXIT(int status);
static int       run_SYS_EXEC(const char* cmd_line);
static int       run_SYS_WAIT(int pid);
static bool      run_SYS_CREATE(const char *file, unsigned initial_size);
static bool      run_SYS_REMOVE(const char *file);
static int       run_SYS_OPEN(const char *file);
static int       run_SYS_FILESIZE(int fd);
static int       run_SYS_READ(int fd, void *buffer, unsigned size);
static int       run_SYS_WRITE(int fd, const void *buffer, unsigned size);
static void      run_SYS_SEEK(int fd, unsigned position);
static unsigned  run_SYS_TELL(int fd);
static void      run_SYS_CLOSE(int fd);

static struct lock lock;

void
syscall_init (void)
{
  lock_init(&lock); 
  intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall");
}

static void
syscall_handler (struct intr_frame *f)
{

  /*
   * Algorithm
   *   1. get the system call number from f->esp (#check if valid#) [3.5.2]
   *   2. find the number of arguments that system call has
   *   	  	//can make find_num_arg more efficient with hashmap
   *   	  	//or an array w/ i = syscall_num and its value = num_arg???
   *
   *   3. for each argument (f->esp+1, esp+2 and esp+3) [3.5.2]
   *      check if it is a valid user address 
   */
  check_pointer(f->esp);

  int syscall_number = *(int*)f->esp;

  switch (syscall_number)
  {
    case SYS_HALT:
      shutdown_power_off();
      break;

    case SYS_EXIT:
      if(check_pointer((void*) ARG1))
      	run_SYS_EXIT(*(int*) ARG1);
      break;

    case SYS_CREATE:
      if (check_pointer(*(void**) ARG1) && check_pointer((void*)ARG2))
      {
          f->eax =  run_SYS_CREATE(*(char**)ARG1, *(unsigned*)ARG2);
	 // printf("%d", f->eax);
      }
      break;

    case SYS_TELL:
      if(check_pointer((void*)ARG1)){
        f->eax = run_SYS_TELL(*(int*) ARG1);
      }
      break; 

    case SYS_WRITE:
      if (check_pointer(*(void**) ARG2))
      {
        f->eax = run_SYS_WRITE(*(int*) ARG1, *(void**) ARG2, *(unsigned*) ARG3);
      }
      break;

    case SYS_EXEC:
    	if (check_pointer(*(void**) ARG1))
    	{
    	  f->eax = run_SYS_EXEC(*(char**) ARG1);
    	}
        break; 

    case SYS_REMOVE:
    	if (check_pointer(*(void**) ARG1))
    	{
    	  f->eax = run_SYS_REMOVE(*(char**) ARG1);
    	}
        break; 

    case SYS_OPEN:
       if (check_pointer(*(void**) ARG1))
       {
             f->eax = run_SYS_OPEN(*(char**) ARG1); 
       }
       break; 

    case SYS_WAIT:
        if(check_pointer((void*)ARG1)){
       		f->eax = run_SYS_WAIT(*(int *) ARG1); 
	}
        break;

    case SYS_FILESIZE:
        f->eax = run_SYS_FILESIZE(*(int *) ARG1); 
        break;

    case SYS_READ: 
        if (check_pointer(*(void**) ARG2))
        {
           f->eax = run_SYS_READ(*(int*) ARG1, *(char**) ARG2, *(unsigned*) ARG3);
        }
        break; 

    case SYS_SEEK:
        f->eax = 0;
        run_SYS_SEEK(*(int *) ARG1, *(unsigned *) ARG2); 
        break; 

    case SYS_CLOSE:
      	break; 

    default:
      f->eax = -1;
      run_SYS_CLOSE(-1);
  }
}

/*
static int
find_num_arg(uint32_t vec_no)
{
  switch (vec_no)
  {
    case SYS_HALT:
      return 0;
    case SYS_EXIT:
      return 1;
    case SYS_EXEC:
      return 1;
    case SYS_WAIT:
      return 1;
    case SYS_CREATE:
      return 2;
    case SYS_OPEN:
      return 1;
    case SYS_FILESIZE:
      return 1;
    case SYS_READ:
      return 3;
    case SYS_WRITE:
      return 3;
    case SYS_SEEK:
      return 2;
    case SYS_TELL:
      return 1;
    case SYS_CLOSE:
      return 1;
    default:
      return -1; 
  }
}
*/
static bool
check_pointer(void* point)
{ 
    uint32_t* phys_addr = pagedir_get_page(thread_current()->pagedir, point);
 
    if(phys_addr != NULL)
    {
      return true; 
    } 
    else
    {
      //pagedir_destroy(thread_current()->pagedir);
      run_SYS_EXIT(-1);
      return false;
    }

}

static void
run_SYS_EXIT(int status)
{
  int i;
  int totalFiles = FD_MAX; // Hard coded still
 
 // Run through the fd table 
 for (i = 0; i < totalFiles; i++)
  {
    struct file* file = thread_current()->fd_table[i];
    if(file != NULL)
    {
      run_SYS_CLOSE(i);
    }
  }
  thread_current()->retval = status; 
  printf ("%s: exit(%d)\n", thread_name(), thread_current()->retval);
  thread_exit();
}

static int
run_SYS_EXEC(const char* cmd_line)
{
  //run the exe in cmd line, pass any given args and returns pid
  if (cmd_line != NULL)
  {
   int ret = process_execute(cmd_line);
   return ret;
  }
  return thread_current()->tid;
}

static int
run_SYS_WAIT(int pid)
{
  return process_wait(pid);
}

static bool
run_SYS_CREATE(const char *file, unsigned initial_size)
{
  if (*file == '\0')
    return false; 
   
  bool result = filesys_create(file, initial_size);
  //printf("file create returned %d\n", result); 
  return result; 
}

static bool
run_SYS_REMOVE(const char *file)
{
    if (file != NULL)
    {
      if(!lock_held_by_current_thread(&lock))
      	lock_acquire(&lock);
      bool ret = filesys_remove(file);
      lock_release(&lock);
      return ret;
    }
    return -1;
}

static int
run_SYS_OPEN(const char *file)
{
 /*
  // Create the inode if it does not exist
  // Get the inode if it does exist

  // Insert the file into the fd_table in the thread*/
  if (file != NULL)
  {
    if(!lock_held_by_current_thread(&lock))
      lock_acquire(&lock);
    struct file *ret = filesys_open(file);
    
    if(ret == NULL){
      lock_release(&lock);
      return -1;
    }
    else {
	
      int fd;
      for (fd=2; fd < FD_MAX; fd++) // TODO: remove hardcoded length value
      {
        if(thread_current()->fd_table[fd] == NULL)
        {
          thread_current()->fd_table[fd] = ret;
	  lock_release(&lock);
          return fd;
        }
      }

    lock_release(&lock);

    }
  }
  return -1;
}

static int
run_SYS_FILESIZE(int fd)
{
  struct file* file = thread_current()->fd_table[fd];
  return file_length(file);
}

static int
run_SYS_READ(int fd, void *buffer, unsigned size)
{
  if(!lock_held_by_current_thread(&lock))
    lock_acquire(&lock);

  unsigned i;
  if (buffer == (void*)NULL || fd < 0 || fd > FD_MAX){
     lock_release(&lock);
     return -1; 
  }
  if (fd == 0)
  {
    for (i = 0; i != size; i++)
    {
      //input_getc() returns an uint8_t
      ((uint8_t*)buffer)[i] = input_getc();
    }
    lock_release(&lock);
    return size;   
  }else
  {

    struct file* file = thread_current()->fd_table[fd];
    if(file == NULL) {
      lock_release(&lock);
      return -1;
    }
    int bytes_read = file_read(file, buffer, size);
    if(bytes_read >= 0){
      lock_release(&lock);
      return bytes_read;
    }
  }
  lock_release(&lock);
  return -1;
}

static int
run_SYS_WRITE(int fd, const void *buffer, unsigned size)
{
  if(!lock_held_by_current_thread(&lock))
    lock_acquire(&lock);

  if (buffer == (void*)NULL)
  {
    lock_release(&lock);
    return -1;
  }
  if (fd <= 0 || fd > FD_MAX)
  {
    lock_release(&lock);
    return -1;
  }
  else if (fd == 1)
  {
    putbuf(buffer, size);
    lock_release(&lock);
    return size; 
  }
  else
  {
    struct file* fp = thread_current()->fd_table[fd];

    if (fp == NULL)
    {
      lock_release(&lock);
      return -1;
    }

    file_write (fp, buffer, size);
    lock_release(&lock);
    return size;
  }
  
  lock_release(&lock);
  return -1;
}

static void
run_SYS_SEEK(int fd, unsigned position)
{
  file_seek(thread_current()->fd_table[fd], position);
}

static unsigned
run_SYS_TELL(int fd)
{
  
  if (fd == 0 || fd == 1)
    return -1;

  struct file* fp = thread_current()->fd_table[fd];
 
  if (fp == NULL)
    return -1;

  return file_tell(fp);
}

static void
run_SYS_CLOSE(int fd)
{
  fd++; // to get rid of the warning
}
